<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Canvas</title>
    <link rel="stylesheet" href="style.css">
    <style>
        canvas {
            border: 1px solid #ccc;
            margin: 0em 1em .4em;
        }
    </style>
    <script>
        const $ = el => document.querySelector(el);
        const $ctx = el => $(el).getContext('2d');
    </script>
</head>
<body>
    <h1>Canvas</h1>
    <p>本文介绍 2D 绘图上下文的<a href="#fill-stroke">填充和描边</a>、<a href="#path">绘制路径</a>、<a href="#text">绘制文本</a>。</p>
    <p><code>canvas</code> 是一种可以绘制图形的元素，使用前需要设定宽高尺寸。</p>
    <pre>&lt;canvas id="example" width="100" height=100&gt;&lt;/canvas&gt;</pre>
    <p>使用 <code>getContext('2d')</code> 获取二维上下文。</p>
<pre>const canvas = document.querySelector("#example");
const context = canvas.getContext("2d");
</pre>

    <p>如果需要导出图像，可以使用 <strong>canvas</strong> 对象的 <a href="https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL" target="_blank" rel="noopener noreferrer"><code>toDataURL(mimeType)</code></a> 方法：</p>
    <pre>const src = canvas.toDataURL("image/png");</pre>
    <script>
        document.addEventListener('click', (e) => {
            if (e.target.tagName === "CANVAS") {
                const uri = e.target.toDataURL("image/png");
                console.log(e.target.id, uri);
            }
        });
    </script>

    <h2 id="fill-stroke">填充和描边</h2>
    <p>使用 <code>fillStyle</code> 设置填充颜色，<code>strokeStyle</code> 设置描边颜色。</p>
    <pre>context.fillStyle = "red";
context.strokeStyle = "rgba(0,0,255,.5)";</pre>
    <p>为了展示填充和描边效果，可以绘制简单矩形。命令如下：</p>
    <pre>// 填充矩形
context.fillRect(x, y, width, height);
// 绘制矩形边框
context.strokeRect(x, y, width, height);
// 擦除一个矩形区域
context.clearRect(x, y, width, height);</pre>
    <canvas id="c1" width="200" height="200"></canvas>
    <script>
        {
            const ctx = $ctx("#c1");
            ctx.fillStyle = "red";
            ctx.fillRect(10, 10, 100, 100);

            ctx.fillStyle = "rgba(0, 255, 0, .8)";
            ctx.fillRect(40, 40, 100, 100);

            ctx.strokeStyle = "rgba(0, 0, 255, .5)";
            ctx.strokeRect(80, 80, 100, 100);

            ctx.clearRect(90, 90, 10, 10);
        }
    </script>

    <p>线条宽度通过 <code>lineWidth</code> 调整。线条末端形状是 <code>lineCap</code>，可选值 <code>butt | round | square</code>。线条相交形状通过 <code>lineJoin</code> 修改，可选值 <code>round | bevel | miter</code>。</p>

    <canvas id="c2" width="200" height="200"></canvas>
    <script>
        {
            const ctx = $ctx("#c2");
            for (let i = 0; i < 5; i++) {
                ctx.beginPath();
                ctx.lineWidth = i + 1;
                ctx.moveTo(10, 10 + i * 15);
                ctx.lineTo(190, 10 + i * 15);
                ctx.stroke();
            }

            function drawLineCap(capType, y, ctx) {
                ctx.beginPath();
                ctx.lineWidth = 10;
                ctx.strokeStyle = "green";
                ctx.lineCap = capType;
                ctx.moveTo(10, y);
                ctx.lineTo(90, y);
                ctx.stroke();

                ctx.font = "12px Arial";
                ctx.textAlign = "center";
                ctx.fillText(capType, 50, y + 15);
            }

            ['butt', 'round', 'square'].forEach((capType, idx) => {
                drawLineCap(capType, 100 + 30 * idx, ctx);
            });

            function drawLineJoin(joinType, y, ctx) {
                ctx.beginPath();
                ctx.lineWidth = 10;
                ctx.strokeStyle = 'steelblue';
                ctx.lineJoin = joinType;
                ctx.moveTo(110, y);
                ctx.lineTo(190, y);
                ctx.lineTo(190, y + 10);
                ctx.stroke();

                ctx.font = "12px Arial";
                ctx.textAlign = "center";
                ctx.fillText(joinType, 150, y + 15);
            }

            ['round', 'bevel', 'miter'].forEach((joinType, idx) => {
                drawLineJoin(joinType, 100 + 30 * idx, ctx);
            });
        }
    </script>

    <h2 id="path">绘制路径</h2>
    <p>使用 <code>beginPath()</code> 开启一个新的路径。一个路径只有一种线条颜色，如果想绘制多种样式的线条，需要创建多条的路径。</p>
    <p>使用 <code>closePath()</code> 向起点绘制封闭线。</p>
    <p>绘制直线使用 <code>moveTo(x, y)</code> 和 <code>lineTo(x, y)</code>。<code>stroke()</code> 使用 <code>strokeStyle</code> 样式绘制线条，而 <code>fill()</code> 使用 <code>fillStyle</code> 填充封闭区域。</p>
    <pre>context.beginPath();
context.moveTo(10, 30);
context.lineTo(190, 90);
context.lineTo(190, 50);
context.closePath();
context.stroke();
context.fill();
</pre>
    <canvas id="c3" width="200" height="100"></canvas>
    <script>
        {
            const ctx = $ctx("#c3");

            ctx.beginPath();
            ctx.lineWidth = 10;
            ctx.strokeStyle = "red";
            ctx.moveTo(10, 10);
            ctx.lineTo(190, 10);
            ctx.stroke();

            ctx.beginPath();
            ctx.fillStyle = "rgba(255, 255, 0, .4)";
            ctx.lineWidth = 5;
            ctx.strokeStyle = "green";
            ctx.moveTo(10, 30);
            ctx.lineTo(190, 90);
            ctx.lineTo(190, 50);
            ctx.closePath();
            ctx.stroke();
            ctx.fill();
        }
    </script>
    <p>绘制弧形使用 <code>arc(x, y, radius, startAngle, endAngle, counterclosewise)</code>：</p>
    <canvas id="c4" width="200" height="200"></canvas>
    <script>
        {
            const ctx = $ctx("#c4");
            ctx.lineWidth = 1;
            ctx.strokeStyle = "blue";
            ctx.arc(100, 100, 95, 0, Math.PI * 2);
            ctx.moveTo(191, 100);
            ctx.arc(100, 100, 91, 0, Math.PI * 2);
            ctx.moveTo(100, 100);
            ctx.lineTo(100, 15);
            ctx.moveTo(100, 100);
            ctx.lineTo(40, 100);
            ctx.stroke();
        }
    </script>

    <h2 id="text">绘制文本</h2>
    <p>绘制文本使用 <code>fillText()</code> 或 <code>strokeText()</code>。常用的文本属性值有：</p>
    <ul>
        <li><code>font</code> 设定文本样式、大小及字体</li>
        <li><code>textAlign</code> 设定水平对齐方式。可选值有：<code>start | end | left | right | center</code></li>
        <li><code>textBaseline</code> 设定垂直对齐方式。可选值有：<code>top | hanging | middle | alphabetic | ideographic | bottom</code></li>
    </ul>
    <pre>ctx.font = "36px Arial";
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.fillText("fillText", 10, 30);
ctx.strokeText("strokeText", 10, 70);

ctx.textAlign = "center";
ctx.textBaseline = "middle";
</pre>
    <canvas id="c5" width="200" height="200"></canvas>
    <script>
        {
            const ctx = $ctx("#c5");

            ctx.font = "36px Arial";
            ctx.fillStyle = "red";
            ctx.strokeStyle = "blue";
            ctx.fillText("fillText", 10, 30);
            ctx.strokeText("strokeText", 10, 70);

            ctx.font = "24px Helvetica";
            ctx.fillStyle = "steelblue";
            ctx.fillText("24px", 10, 100);
            ctx.font = "18px Helvetica";
            ctx.fillText("18px", 10, 130);

            ctx.beginPath();
            ctx.strokeStyle = "rgba(0, 0, 255, .5)";
            ctx.moveTo(140, 80);
            ctx.lineTo(140, 134);
            ctx.stroke();

            ctx.fillStyle = "orange";

            ["start", "center", "end"].forEach((align, idx) => {
                ctx.textAlign = align;
                ctx.fillText(align, 140, 90 + idx * 20);
            });

            ctx.beginPath();
            ctx.strokeStyle = "rgba(255,0,0,.5)";
            const lineY = 170;
            ctx.moveTo(10, lineY);
            ctx.lineTo(190, lineY);
            ctx.stroke();

            ctx.textAlign = "start";
            ctx.fillStyle = "blue";
            ["top", "middle", "bottom"].forEach((baseline, idx) => {
                ctx.textBaseline = baseline;
                ctx.fillText(baseline, 10 + 50 * idx, lineY);
            });
        }
    </script>
</body>
</html>